// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim

#pragma once

#include <algorithm>
#include <functional>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include <utility>

#include "coin2/base/string_util.h"
#include "coin2/exchange/base/feed/processor.h"
#include "coin2/exchange/feed.pb.h"
#include "coin2/exchange/okex_common/api_util/auth_key.h"

namespace coin2::exchange::okex_common::feed_v5 {
namespace impl {

using coin2::exchange::feed::WebsocketConfig;

class OkexCommonRequestGenerator : public base::feed::RequestGenerator {
 public:
  explicit OkexCommonRequestGenerator(const WebsocketConfig* ws_config)
      : config_(ws_config) {}
  std::string GenerateAuthRequest(const std::string& key_filepath) override {
    static OkexWsAuth auth(key_filepath);
    return auth.CreateSignature("v5");
  }

  std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) override {
    std::vector<std::string> reqs;

    std::vector<std::string> reqs_kline;
    std::vector<std::string> ch = channels;
    std::vector<std::string> ch_kline;
    auto iter = ch.begin();
    while (iter != ch.end()) {
      if (StringStartsWith(*iter, "kline_rest")) {
        ch_kline.push_back(*iter);
        iter = ch.erase(iter);
      } else {
        iter++;
      }
    }
    if (!ch_kline.empty()) {
      auto timestamp = MarketTimestampProvider::GetTimestamp();
      boost::posix_time::ptime round_time = TimestampRoundHour(timestamp);
      for (auto& ch_k : ch_kline) {
        std::string c = channel_templates.at(ch_k);
        auto interval_time = GetGranularity(c);
        for (auto& symbol : symbols) {
          std::vector<std::pair<int64_t, int64_t>> durations;
          bool long_query = base::feed::RequestGenerator::CheckLongQuery(
              round_time, symbol, ch_k, &last_long_query_time_);
          if (long_query) {
            durations = base::feed::RequestGenerator::GetDurations(
                config_->kline_length(), config_->kline_start(), config_->kline_end(),
                interval_time, 100);
          } else {
            // query small data
            durations = base::feed::RequestGenerator::GetDurations(
                "3", "", "",interval_time, 100);
          }
          int idx = 0;
          for (auto& d : durations) {
            std::string c_copy = c;
            boost::replace_all(c_copy, "{}", symbol);
            boost::replace_all(c_copy, "{end}", std::to_string(d.second / 1'000'000L));
            boost::replace_all(c_copy, "{start}", std::to_string(d.first / 1'000'000L));
            if (long_query) {
              c_copy.append("&page=").append(std::to_string(idx));
            }
            reqs_kline.emplace_back(c_copy);
            idx++;
          }
        }
      }
    }
    reqs.insert(reqs.end(), reqs_kline.begin(), reqs_kline.end());

    std::vector<std::string> reqs_others;
    if (request_template.find("{json_list}") != std::string::npos) {
      for (const auto& symbol : symbols) {
        std::vector<std::string> str_jsons;
        for (const auto& channel_name : ch) {
          std::string c = channel_templates.at(channel_name);
          if (channel_name == "index") {
            auto symbol_parts = Split(symbol, "-");
            if (symbol_parts.size() == 2) {
              boost::replace_all(c, "{}", symbol);
            } else if (symbol_parts.size() == 3) {
              boost::replace_all(c, "{}",
                                 symbol_parts[0] + "-" + symbol_parts[1]);
            } else {
              LOG(ERROR) << "[Okex v5 Request Generator]: Channel Index, wrong symbol" << symbol;
            }
          } else {
            boost::replace_all(c, "{}", symbol);
          }
          str_jsons.push_back(c);
        }
        if (str_jsons.size() == 0) continue;
        std::string s = request_template;
        boost::replace_all(s, "{json_list}", "[" + Join(str_jsons) + "]");
        reqs_others.push_back(s);
      }
    }

    reqs.insert(reqs.end(), reqs_others.begin(), reqs_others.end());
    return reqs;
  }

 private:
  int64_t GetGranularity(const std::string& channel) {
    if (channel.find("bar=1m") != std::string::npos) {
      return (int64_t)60 * 1'000'000'000;
    }else if (channel.find("bar=3m") != std::string::npos) {
      return (int64_t)3 * 60 * 1'000'000'000;
    } else if (channel.find("bar=5m") != std::string::npos) {
      return (int64_t)5 * 60 * 1'000'000'000;
    } else if (channel.find("bar=15m") != std::string::npos) {
      return (int64_t)15 * 60 * 1'000'000'000;
    } else if (channel.find("bar=30m") != std::string::npos) {
      return (int64_t)30 * 60 * 1'000'000'000;
    } else if (channel.find("bar=1H") != std::string::npos) {
      return (int64_t)60 * 60 * 1'000'000'000;
    } else if (channel.find("bar=2H") != std::string::npos) {
      return (int64_t)2 * 60 * 60 * 1'000'000'000;
    } else if (channel.find("bar=4H") != std::string::npos) {
      return (int64_t)4 * 60 * 60 * 1'000'000'000;
    } else if (channel.find("bar=1Dutc&") != std::string::npos) {
      return (int64_t)24 * 3600 * 1'000'000'000;
    } else if (channel.find("bar=1Wutc&") != std::string::npos) {
      return (int64_t)7* 24 * 3600 * 1'000'000'000;
    } else if (channel.find("bar=1Mutc&") != std::string::npos) {
      return (int64_t)28 * 24 * 3600 * 1'000'000'000;
    } else {
      THROW() << "Unkonw new channel: " << channel;
    }

    return std::numeric_limits<int64_t>::quiet_NaN();
  }

  const WebsocketConfig* config_;
};

}  // namespace impl
using impl::OkexCommonRequestGenerator;
}  // namespace coin2::exchange::okex_common::feed_v5
